using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace DrawTools.Editor.CanvasFigures
{
  /// <summary>
  ///     Line graphic object
  /// </summary>
  public class CanvasArrow : CanvasObject
  {
    private const int HOT_SPOT_SIZE = 7;

    private Point _endPoint;
    private bool _isMiddlePointSet;
    private Point _middlePoint;
    private Point _startPoint;
    private Rectangle _rectangle;

    public override int HandlesCount
    {
      get { return 3; }
    }

    public bool IsArrowAtStart { get; set; }
    public bool IsArrowAtEnd { get; set; }

    protected GraphicsPath AreaPath { get; set; }

    protected Region AreaRegion { get; set; }
    private Pen AreaPen { get; set; }
    public CanvasArrow() : this(0, 0, 1, 1) {}

    public CanvasArrow(int x1, int y1, int x2, int y2)
    {
      AreaRegion = null;
      AreaPen = null;
      AreaPath = null;

      _startPoint.X = x1;
      _startPoint.Y = y1;
      _endPoint.X = x2;
      _endPoint.Y = y2;
      _middlePoint = MidPoint(_startPoint, _endPoint);

      IsArrowAtEnd = true;
    }

    public override Rectangle GetBounds()
    {
      return _rectangle;
    }

    public override void Draw(Graphics graphics)
    {
      using (var pen = CreateBorderPen())
      {
        var points = GetLinePoints();

        using (var graphicsPath = new GraphicsPath())
        {
          graphicsPath.AddCurve(points, 0.7f);
          DrawFuzzyPath(graphics, graphicsPath, BorderColor);

          var rectF = graphicsPath.GetBounds();
          _rectangle = new Rectangle((int)rectF.X, (int)rectF.Y, (int)rectF.Width, (int)rectF.Height);
        }

        graphics.DrawCurve(pen, points, 0.7f);
      }
    }

    /// <summary>
    ///     Hit test.
    ///     Return value: -1 - no hit
    ///     0 - hit anywhere
    ///     > 1 - handle number
    /// </summary>
    public override int HitTest(Point point)
    {
      if (IsSelected)
      {
        for (int i = 1; i <= HandlesCount; i++)
        {
          if (GetHandleRectangle(i).Contains(point))
            return i;
        }
      }

      if (PointInObject(point))
        return 0;

      return -1;
    }

    public override bool IntersectsWith(Rectangle rectangle)
    {
      CreateObjects();

      return AreaRegion.IsVisible(rectangle);
    }

    public override Cursor GetHandleCursor(int handleNumber)
    {
      switch (handleNumber)
      {
        case 1:
        case 2:
        case 3:
          return Cursors.SizeAll;
        default:
          return Cursors.Default;
      }
    }

    public override void MoveHandleTo(Point point, int handleNumber, bool shiftPressed)
    {
      if (handleNumber == 1)
        _startPoint = point;
      else if (handleNumber == 2)
      {
        _middlePoint = point;
        _isMiddlePointSet = true;
      }
      else
        _endPoint = point;

      if (!_isMiddlePointSet)
        _middlePoint = MidPoint(_startPoint, _endPoint);

      Invalidate();
    }

    public override void Move(int deltaX, int deltaY)
    {
      _startPoint.X += deltaX;
      _startPoint.Y += deltaY;

      _middlePoint.X += deltaX;
      _middlePoint.Y += deltaY;

      _endPoint.X += deltaX;
      _endPoint.Y += deltaY;

      Invalidate();
    }

    /// <summary>
    ///   Create graphic objects used from hit test.
    /// </summary>
    protected virtual void CreateObjects()
    {
      if (AreaPath != null)
        return;

      // Create path which contains wide line
      // for easy mouse selection
      AreaPath = new GraphicsPath();
      AreaPen = new Pen(Color.Black, HOT_SPOT_SIZE);

      AreaPath.AddCurve(GetLinePoints(), 0.7f);
      AreaPath.Widen(AreaPen);

      // Create region from the path
      AreaRegion = new Region(AreaPath);
    }

    protected override Pen CreateBorderPen()
    {
      var pen = base.CreateBorderPen();

      var bigArrow = new AdjustableArrowCap(5, 5);

      if (IsArrowAtStart)
        pen.CustomStartCap = bigArrow;

      if (IsArrowAtEnd)
        pen.CustomEndCap = bigArrow;

      return pen;
    }

    /// <summary>
    ///   Get handle point by 1-based number
    /// </summary>
    /// <param name="handleNumber"></param>
    /// <returns></returns>
    protected override Point GetHandle(int handleNumber)
    {
      if (handleNumber == 1)
        return _startPoint;

      if (handleNumber == 2)
        return _middlePoint;

      return _endPoint;
    }

    protected override bool PointInObject(Point point)
    {
      CreateObjects();

      return AreaRegion.IsVisible(point);
    }

    /// <summary>
    /// Invalidate object.
    /// When object is invalidated, path used for hit test
    /// is released and should be created again.
    /// </summary>
    protected void Invalidate()
    {
      if (AreaPath != null)
      {
        AreaPath.Dispose();
        AreaPath = null;
      }

      if (AreaPen != null)
      {
        AreaPen.Dispose();
        AreaPen = null;
      }

      if (AreaRegion != null)
      {
        AreaRegion.Dispose();
        AreaRegion = null;
      }
    }

    private static Point MidPoint(Point controlPoint1, Point controlPoint2)
    {
      return new Point((controlPoint1.X + controlPoint2.X) / 2, (controlPoint1.Y + controlPoint2.Y) / 2);
    }

    private Point[] GetLinePoints()
    {
      return new[]
      {
        _startPoint,
        _middlePoint,
        _endPoint
      };
    }
  }
}